/* This file is part of EasyTest CodeGen, a project to generate JUnit test cases from source code in EasyTest Template format and helping to keep them in sync during refactoring. EasyTest CodeGen, a tool provided by EaseTech Organization Under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt */ package org.easetech.easytest.codegen; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.sun.javadoc.ClassDoc; import com.sun.javadoc.FieldDoc; import com.sun.javadoc.MethodDoc; import com.sun.javadoc.PackageDoc; import com.sun.javadoc.Parameter; import com.sun.javadoc.Type; /** * An implementation of ITestingStrategy * It contains the logic for test case generation for suite, class, method etc.. * Fetches the required variables for template from ClassDoc, * Populate those variables and apply on template to generate the target test cases * * @author Ravi Polampelli * */ public class TestingStrategy extends ConfigurableStrategy implements ITestingStrategy, JUnitDocletProperties { /** * An instance of logger associated. */ public static final Logger LOG = LoggerFactory.getLogger(TestingStrategy.class); // TODO Accessor Tests fuer Enumerations protected static final String TESTSUITE_SUITE_METHOD_NAME = "suite"; protected static final String JUNIT_TEST_CLASS_NAME = "junit.framework.Test"; protected static final String ACCESSOR_STARTS_WITH[][] = {{"set", "get"},{"set", "is"}}; protected static int INDEX_SET = 0; protected static int INDEX_GET = 1; private static String[] requiredStrings = null; public static final String[] MINIMUM_MARKER_SET = { VALUE_MARKER_IMPORT_BEGIN, VALUE_MARKER_IMPORT_END, VALUE_MARKER_EXTENDS_IMPLEMENTS_BEGIN, VALUE_MARKER_EXTENDS_IMPLEMENTS_END, VALUE_MARKER_CLASS_BEGIN, VALUE_MARKER_CLASS_END }; public void init() { super.init(); setProperties(null); } /** * * Checks if package that is encapsulated in package doc is testable * It mainly checks if PackageDoc name is test package name. * * @param doc the package doc * @param naming naming strategy object, used to find out test package name */ public boolean isTestablePackage(PackageDoc doc, INamingStrategy naming) { boolean returnValue; returnValue = (doc != null); returnValue = returnValue && (naming != null) && !naming.isTestPackageName(doc.name()); return returnValue; } /** * * Checks if class that is encapsulated in class doc is testable * It mainly checks if class is not abstract, interface, protected, private, annotated, enum or itself is test etc... * not a suite method * and it is a public class * * @param doc the class doc * @param naming naming strategy object, used to find out test class name */ public boolean isTestableClass(ClassDoc doc, INamingStrategy naming) { boolean returnValue; returnValue = (doc != null); returnValue = returnValue && !doc.isAbstract(); returnValue = returnValue && !doc.isInterface(); returnValue = returnValue && !doc.isProtected(); returnValue = returnValue && !doc.isPrivate(); returnValue = returnValue && !doc.isAnnotationType(); returnValue = returnValue && !doc.isEnum(); returnValue = returnValue && !isInnerClass(doc); returnValue = returnValue && doc.isPublic(); returnValue = returnValue && !isATest(doc); returnValue = returnValue && (naming != null) && !naming.isTestClassName(doc.qualifiedName()); returnValue = returnValue && !hasSuiteMethod(doc); return returnValue; } /** * * Checks if method that is encapsulated in method doc is testable * It mainly checks if method is not abstract, protected, private, annotated, enum const * It is a public method * * @param doc the class doc * @param naming naming strategy object, used to find out test class name */ public boolean isTestableMethod(MethodDoc doc) { boolean returnValue; returnValue = (doc != null); returnValue = returnValue && !doc.isAbstract(); returnValue = returnValue && !doc.isProtected(); returnValue = returnValue && !doc.isPrivate(); returnValue = returnValue && !doc.isAnnotationTypeElement(); returnValue = returnValue && !doc.isEnumConstant(); returnValue = returnValue && doc.isPublic(); return returnValue; } /** * * Generates test suite code * It gets the test suite properties (sub package test suites, test classes, suite name etc..) * and loads the required template part and apply the test suite properties on this template * to get the actual test suite code * * @param testSuiteVO the test suite value object * @param indexPackage this is index of PackageDoc of PackageDoc array contained * in testSuiteVO for which test suit to be generated. */ public boolean codeTestSuite(TestSuiteVO testSuiteVO,int indexPackage) { LOG.info("codeTestSuite started"); boolean returnValue; Properties addProps; String template; PackageDoc[] packageDocs = testSuiteVO.getPackageDocs(); returnValue = (packageDocs != null); returnValue = returnValue && (indexPackage >= 0); returnValue = returnValue && (indexPackage < packageDocs.length); returnValue = returnValue && (testSuiteVO.getNaming() != null); returnValue = returnValue && (testSuiteVO.getNewCode() != null); returnValue = returnValue && (testSuiteVO.getProperties() != null); if (returnValue) { returnValue = isTestablePackage(packageDocs[indexPackage], testSuiteVO.getNaming()); if (returnValue) { // test this package addProps = getTestSuiteProperties(testSuiteVO,indexPackage); template = getTemplate(addProps, "testsuite", addProps.getProperty(TEMPLATE_NAME)); if(isTestSuiteExist(addProps) || isTestCaseExist(addProps)) { testSuiteVO.getNewCode().append(StringHelper.replaceVariables(template, addProps)); } else { LOG.info("There are no test classes for your preference " + "in this suite hence no test suite will be generated:"+packageDocs[indexPackage].name()); } } // no else } else { printError("TestingStrategy.codeTestSuite() parameter error"); } LOG.info("codeTestSuite finished,returnValue:"+returnValue); return returnValue; } private boolean isTestCaseExist(Properties addProps) { boolean isTestCaseExist = false; if(addProps.getProperty(TESTSUITE_ADD_TESTCASES)!=null && !"".equals(addProps.getProperty(TESTSUITE_ADD_TESTCASES))){ isTestCaseExist = true; } return isTestCaseExist; } private boolean isTestSuiteExist(Properties addProps) { boolean isTestSuiteExist = false; if(addProps.getProperty(TESTSUITE_ADD_TESTSUITES)!=null && !"".equals(addProps.getProperty(TESTSUITE_ADD_TESTSUITES))){ isTestSuiteExist = true; } return isTestSuiteExist; } /** * * Generates test class/case code * It gets the test case properties (test methods, parameters, names etc..) * and loads the required template part then apply the test case properties on this template * to get the actual test class/case code * * @param testCaseVO the test case value object */ public boolean codeTestCase(TestCaseVO testCaseVO) { LOG.info("codeTestCase started"); boolean returnValue; Properties addProps; String template; ClassDoc classDoc = testCaseVO.getClassDoc(); // check if all parameters are non-null returnValue = (testCaseVO.getClassDoc() != null); returnValue = returnValue && (testCaseVO.getPackageDoc() != null); returnValue = returnValue && (testCaseVO.getNaming() != null); returnValue = returnValue && (testCaseVO.getNewCode() != null); returnValue = returnValue && (testCaseVO.getProperties() != null); if (returnValue) { returnValue = isTestableClass(classDoc, testCaseVO.getNaming()); if (returnValue) { // test this class addProps = getTestCaseProperties(testCaseVO); template = getTemplate(addProps, "testcase", addProps.getProperty(TEMPLATE_NAME)); LOG.debug("getTestCaseProperties:"+addProps); LOG.debug("template:"+template); if(addProps.getProperty(TESTCASE_TESTMETHODS)!=null && !"".equals(addProps.getProperty(TESTCASE_TESTMETHODS))) { testCaseVO.getNewCode().append(StringHelper.replaceVariables(template, addProps)); } else { LOG.info("There are no test methods for your preference " + "in this class hence no test case will be generated:"+testCaseVO.getClassDoc().name()); } } // no else } else { printError("TestingStrategy.codeTestCase() parameter error"); } LOG.info("codeTestCase finished,returnValue:"+returnValue); return returnValue; } /** * * Generates test method code in a test class * It gets the test method properties (parameters, types, return types, names etc..) * * and loads the required template part then apply the test method properties on this template * to get the actual test method code * There are two templates used * 1) void return types * 2) non-void return types - this is introduced to capture the result/output data from test method * * @param testCaseVO the test case value object * @param testMethodVO the test case value object */ public boolean codeTest(TestCaseVO testCaseVO, TestMethodVO testMethodVO,int index) { MethodDoc[] methodDocs = testMethodVO.getMethodDocs(); LOG.info("codeTest started,index:"+index+methodDocs[index].name()); boolean returnValue; Properties addProps; String template; // check if all parameters are non-null and index is in range returnValue = (methodDocs != null); returnValue = returnValue && (index >= 0); returnValue = returnValue && (index < methodDocs.length); returnValue = returnValue && (testCaseVO.getClassDoc() != null); returnValue = returnValue && (testCaseVO.getPackageDoc() != null); returnValue = returnValue && (testCaseVO.getNaming() != null); returnValue = returnValue && (testMethodVO.getNewCode() != null); returnValue = returnValue && (testMethodVO.getProperties() != null); LOG.debug("all parameters are non-null and index is in range?:"+returnValue); if (returnValue) { returnValue = isTestableMethod(methodDocs[index]); LOG.debug("isTestableMethod:"+returnValue); if (returnValue) { // test this method addProps = getTestProperties(testCaseVO,testMethodVO,index); //List<Map<String, Object>> methodData = getTestMethodMap(methodDocs, index, classDoc, packageDoc, naming, properties); LOG.debug("getTestProperties:"+addProps); if (addProps != null) { // test if not tested already String returnType = addProps.getProperty(METHOD_RETURNTYPE); if(returnType != null && !returnType.equalsIgnoreCase("void")) { template = getTemplate(addProps, "testmethod", addProps.getProperty(TEMPLATE_NAME)); } else { template = getTemplate(addProps, "testmethodvoid", addProps.getProperty(TEMPLATE_NAME)); } LOG.debug("template:"+template); testMethodVO.getNewCode().append(StringHelper.replaceVariables(template, addProps)); } // no else } // no else } else { printError("TestingStrategy.codeTestCase() parameter error"); } LOG.info("codeTest finished,returnValue:"+returnValue); return returnValue; } /** * Gets test suite properties, sub pcakge test suites, test classes of this package:<br> * \@pre fields of test case vo (classDoc != null) && (packageDoc != null) && (naming != null) && (properties != null) <br> * \@post return != null <br> * @param sourceCode * @param filterProperties * * @return new Properties instance with all properties for parameter 'properties' * and test case specific properties */ public Properties getTestSuiteProperties(TestSuiteVO testSuiteVO, int indexPackage) { Properties returnValue = new Properties(testSuiteVO.getProperties()); returnValue.setProperty(TESTSUITE_PACKAGE_NAME, testSuiteVO.getNaming().getTestPackageName(testSuiteVO.getPackageDocs()[indexPackage].name())); returnValue.setProperty(TESTSUITE_CLASS_NAME, testSuiteVO.getNaming().getTestSuiteName(testSuiteVO.getPackageDocs()[indexPackage].name())); returnValue.setProperty(TEMPLATE_NAME, TEMPLATE_ATTRIBUTE_DEFAULT); returnValue.setProperty(TESTSUITE_ADD_TESTSUITES, getTestSuiteAddTestSuites(testSuiteVO,indexPackage)); returnValue.setProperty(TESTSUITE_ADD_TESTCASES, getTestSuiteAddTestCases(testSuiteVO,indexPackage)); returnValue.setProperty(TESTSUITE_IMPORTS, getTestSuiteImports(testSuiteVO)); returnValue.setProperty(PACKAGE_NAME, testSuiteVO.getPackageDocs()[indexPackage].name()); return returnValue; } /** * Gets test case properties, test methods, registers for converters and editors:<br> * \@pre fields of test case vo (classDoc != null) && (packageDoc != null) && (naming != null) && (properties != null) <br> * \@post return != null <br> * @param testCaseVO * * @return new Properties instance with all properties for parameter 'properties' * and test case specific properties */ public Properties getTestCaseProperties(TestCaseVO testCaseVO) { LOG.info("getTestCaseProperties started,"); Properties returnValue = new Properties(testCaseVO.getProperties()); returnValue.setProperty(TESTCASE_PACKAGE_NAME, testCaseVO.getNaming().getTestPackageName(testCaseVO.getPackageDoc().name())); returnValue.setProperty(TESTCASE_CLASS_NAME, testCaseVO.getNaming().getTestCaseName(testCaseVO.getClassDoc().name())); returnValue.setProperty(TESTCASE_INSTANCE_NAME, testCaseVO.getClassDoc().name().substring(0,1).toLowerCase()+testCaseVO.getClassDoc().name().substring(1)); returnValue.setProperty(TESTCASE_INSTANCE_TYPE, testCaseVO.getClassDoc().qualifiedName()); //List of imports to be added to test case Set<String> importsSet = new HashSet<String>(); TestMethodVO testMethodVO = new TestMethodVO(null,returnValue,importsSet,null,null); // Gets all the test methods of the test case returnValue.setProperty(TESTCASE_TESTMETHODS, getTestMethods(testCaseVO, testMethodVO)); returnValue = testMethodVO.getProperties(); returnValue.setProperty(TESTCASE_METHOD_UNMATCHED, VALUE_METHOD_UNMATCHED_NAME); returnValue.setProperty(TEMPLATE_NAME, TEMPLATE_ATTRIBUTE_DEFAULT); returnValue.setProperty(PACKAGE_NAME, testCaseVO.getPackageDoc().name()); returnValue.setProperty(CLASS_NAME, testCaseVO.getClassDoc().name()); //test data file path, the path will be same as test class, the actual location (root folder) may be different String testDataFilePath = StringHelper.getFilePath(returnValue.getProperty(TESTCASE_PACKAGE_NAME), returnValue.getProperty(TESTCASE_CLASS_NAME),testCaseVO.getLoaderType()); System.out.println("testDataFilePath"+testDataFilePath); returnValue.setProperty(TESTCASE_DATA_FILE_PATH, testDataFilePath); //gets the converter registers to be added in test class @BeforeClass method returnValue.setProperty(TESTCASE_REGISTER_CONVERTERS, getConverterRegisters(returnValue,testCaseVO.getConvertersMap())); //gets the editor registers to be added in test class @BeforeClass method returnValue.setProperty(TESTCASE_REGISTER_EDITORS, getEditorRegisters(returnValue,testCaseVO.getConvertersMap())); returnValue.setProperty(TESTCASE_IMPORTS, codeImports(returnValue,testMethodVO.getImportsSet())); returnValue.setProperty(TESTCASE_LOADER_TYPE, testCaseVO.getLoaderType().toString()); LOG.info("getTestCaseProperties finished,returnValue:"+returnValue); LOG.debug("testData map at testclass level:"+testCaseVO.getTestData()); return returnValue; } /** * Gets the imports template and add all the imports from properties to imports set. * * @param returnValue * @param importsSet * @return */ private String codeImports(Properties returnValue, Set<String> importsSet) { StringBuffer importsListValue = new StringBuffer(); if(importsSet.size()>0){ String template = getTemplate(returnValue, "class", "import"); for(String importClassName :importsSet ){ returnValue.setProperty(PARAM_CLASS_TYPE, importClassName); importsListValue.append(StringHelper.replaceVariables(template, returnValue)); } } LOG.debug("getTestCaseImports:"+importsListValue.toString()); return importsListValue.toString(); } /** * creates test methods for all the valid business methods * It also checks if filters include/exclude are available in the source code, * based on the user choice it decides whether to generate the test case for the method or not. * * @param testCaseVo * @param testMethodVO * @return */ private String getTestMethods(TestCaseVO testCaseVo,TestMethodVO testMethodVO) { LOG.debug("getTestMethods started,"); StringBuffer sb; MethodDoc[] methodDocs; methodDocs = testCaseVo.getClassDoc().methods(false); sb = new StringBuffer(); testMethodVO.setNewCode(sb); for (int i=0; i< methodDocs.length; i++) { if (isTestableMethod(methodDocs[i])) { int startPosition = methodDocs[i].position().line(); int endPosition = 0; if(i+1<methodDocs.length){ endPosition = methodDocs[i+1].position().line()-1; } StringBuffer methodSourceCode = getMethodSourceCode(testCaseVo.getSourceCode(),startPosition,endPosition); testMethodVO.setMethodSourceCode(methodSourceCode); //check the filters then generate test code if(isFiltered(methodSourceCode,testMethodVO.getProperties())) { testMethodVO.setMethodDocs(methodDocs); //actual method to generate test code codeTest(testCaseVo,testMethodVO, i); } } } LOG.debug("getTestMethods finished,"+testMethodVO.getNewCode().toString()); return testMethodVO.getNewCode().toString(); } /** * checks the existence of the configured filters (java statements/strings) in the source code * * @param sourceCode * @param filterProperties * @return */ private boolean isFiltered(StringBuffer sourceCode, Properties filterProperties) { boolean isFiltered = false; LOG.debug("isFiltered started"); String includeFilter = filterProperties.getProperty(FILTER_INCLUDE); String excludeFilter = filterProperties.getProperty(FILTER_EXCLUDE); if(includeFilter != null || excludeFilter != null) { String[] includeFilters = null; String[] excludeFilters = null; if(includeFilter != null) { includeFilters = includeFilter.split(","); } if(excludeFilter != null) { excludeFilters = excludeFilter.split(","); } isFiltered = checkFilter(sourceCode,includeFilters,excludeFilters); } else { isFiltered = true; } LOG.debug("isFiltered finished with value:"+isFiltered); return isFiltered; } /** * Gets the method source code for given class between the lines start and end position. * It loads the source code into String Buffer and gets the substring between the start and end position * @param excludeFilters * @param String fullClassName * @param String path file path * @param int startPosition * @param int endPostion * @return String if successfully loaded or null if either file does not exist or line does not exist. */ private boolean checkFilter(StringBuffer javaClassSource, String[] includeFilters, String[] excludeFilters){ LOG.debug("checkFilter started: startPosition:"); boolean isFiltered = false; String[] sourceCodeLines = javaClassSource.toString().split("\n"); for(int i=0;i<sourceCodeLines.length;i++){ LOG.debug("sourceCodeLines[i]:"+i+", "+sourceCodeLines[i]); //priority to exclude filters if(excludeFilters != null){ for(int j=0;j<excludeFilters.length;j++){ LOG.debug("excludeFilters[j]"+j+excludeFilters[j]); //if there are wildcard characters if(excludeFilters[j].contains("*")) { String[] words = excludeFilters[j].split("\\*"); boolean allWordsExist = true; for(String word:words){ if(!sourceCodeLines[i].contains(word)){ allWordsExist = false; break; } } if(allWordsExist){ return false; } } else if(sourceCodeLines[i].contains(excludeFilters[j])){ return false; } } } //then check include filters if(includeFilters != null){ for(int j=0;j<includeFilters.length;j++){ LOG.debug("includeFilters[j]"+j+includeFilters[j]); if(includeFilters[j].contains("*")) { String[] words = includeFilters[j].split("\\*"); boolean allWordsExist = true; for(String word:words){ if(!sourceCodeLines[i].contains(word)){ allWordsExist = false; break; } } if(allWordsExist){ return true; } } else if(sourceCodeLines[i].contains(includeFilters[j])){ return true; } } } } //after checking include and exclude filters on method source code // if includeFilters are not there then return true if(includeFilters == null) return true; LOG.debug("checkFilter finished with value"+isFiltered); return isFiltered; } /** * gets the method source code from the class source codde between start position and end position * * @param javaClassSource * @param startPosition * @param endPosition * @return */ private StringBuffer getMethodSourceCode(StringBuffer javaClassSource, int startPosition, int endPosition) { String[] sourceCodeLines = javaClassSource.toString().split("\n"); StringBuffer methodSourceCode = new StringBuffer(); if(startPosition <0) startPosition = 0; if(endPosition <= 0) endPosition = sourceCodeLines.length; if(startPosition > 0){ for(int i=startPosition;i<endPosition;i++){ i = skipCommentLines(sourceCodeLines,i); methodSourceCode.append(sourceCodeLines[i]+"\n"); } } return methodSourceCode; } /** * Method to count no.of lines in comment and add commented line index to array * @param sourceCodeLines * @param textIndex * @return */ private int skipCommentLines(String[] sourceCodeLines, int textIndex){ boolean isSkipped = false; String sourceCodeLine = sourceCodeLines[textIndex]; // check if single line comment if(sourceCodeLine.trim().startsWith("//")) { //System.out.println("skipCommentLines starts "); //System.out.println("comments text"+sourceCodeLines); textIndex++; isSkipped = true; //System.out.println("skipCommentLines finished "); } //check for multiple line count, i.e. this line is start of block comment and then count upto close of block comment. if(sourceCodeLine.trim().startsWith("/*")) { //System.out.println("skipCommentLines starts "); //System.out.println(" comments start text"+sourceCodeLines); //increment count textIndex++; //check if line has ending comment "*/" otherwise loop through the lines till ending comment appears while(!sourceCodeLine.contains("*/")) { sourceCodeLine = sourceCodeLines[textIndex]; //increment count textIndex++; } isSkipped = true; } sourceCodeLine = sourceCodeLines[textIndex]; if(isSkipped && sourceCodeLine!= null) { if(sourceCodeLine.trim().startsWith("//") || sourceCodeLine.trim().startsWith("/*") ) { //System.out.println("skipCommentLines recursive started"+sourceCodeLines); textIndex = skipCommentLines(sourceCodeLines,textIndex); //System.out.println("skipCommentLines recursive finished"+recursiveIsSkipped); } isSkipped = true; } return textIndex; } /** * Comment on DBC:<br> * \@pre (methodDoc != null) && (classDoc != null) && (packageDoc != null) && (naming != null) && (properties != null) <br> * @param testData * @param convertersMap * @param sourceCode * @param importsList * * @return if the method specified by 'index' needs a test, new Properties instance with all properties for parameter 'properties' * and test method specific properties; * null if the method specified by 'index' needs no test */ public Properties getTestProperties(TestCaseVO testCaseVO, TestMethodVO testMethodVO, int index) { Properties returnValue = null; StringBuffer signature = null; StringBuffer paramValues = null; Parameter[] parameters = null; MethodDoc[] methodDocs = testMethodVO.getMethodDocs(); returnValue = getTestAccessorProperties(testCaseVO,testMethodVO,index); // returnValue == null means, no test for this accessor if ((returnValue == testMethodVO.getProperties()) && (returnValue != null)) { // not an accessor if (isFirstTestableMethodWithName(methodDocs, index)) { returnValue = new Properties(testMethodVO.getProperties()); returnValue.setProperty(TESTMETHOD_NAME, testCaseVO.getNaming().getTestMethodName(methodDocs[index].name())); //returnValue.setProperty(TEMPLATE_NAME, TEMPLATE_ATTRIBUTE_DEFAULT); returnValue.setProperty(TEMPLATE_NAME, TEMPLATE_ATTRIBUTE_EASYTEST); returnValue.setProperty(METHOD_NAME, methodDocs[index].name()); signature = new StringBuffer(""); paramValues = new StringBuffer(""); testMethodVO.setMethodData(new ArrayList<Map<String,Object>>()); parameters = methodDocs[index].parameters(); LOG.debug("methodDocs[index].position()"+methodDocs[index].position().toString()); LOG.debug("methodDocs[index].position() line"+methodDocs[index].position().line()); LOG.debug("methodDocs[index].position() column"+methodDocs[index].position().column()); //building method data map Map<String, Object> data = new LinkedHashMap<String,Object>(); List<String> mandatoryFields = new ArrayList<String>(); for (int i=0; i<getNumberOfParameters(methodDocs[index]); i++) { if (i>0) { signature.append(", "); paramValues.append(", "); } // no else signature.append("@Param(name=\""+parameters[i].name()+"\")"); signature.append(parameters[i].typeName()+" "); signature.append(parameters[i].name()); paramValues.append(parameters[i].name()); //checking if parameter is of complex type Type paramType = parameters[i].type(); //if parameter is collection or any other type in java.util than Date,Time.. // then we can not create converter hence we can not include that in EasyTest framework String paramTypeName = paramType.qualifiedTypeName(); LOG.debug("Param Type qualifiedTypeName is :"+paramTypeName); if(!isJavaDateType(paramType) && paramTypeName.contains("java.util.")){ return null; } LOG.debug("Param Type is :"+paramType.typeName()); if(paramType != null){ LOG.debug("ParamType is primitive:"+paramType.isPrimitive()); Properties converterProperties = new Properties(returnValue); codeConvertersAndEditors(converterProperties,paramType, testCaseVO,data,parameters[i].name(),testMethodVO,mandatoryFields); if(!isSimpleType(paramType)){ testMethodVO.getImportsSet().add(paramType.qualifiedTypeName()); } } } testMethodVO.getMethodData().add(data); testCaseVO.getTestData().put(returnValue.getProperty(TESTMETHOD_NAME), testMethodVO.getMethodData()); testCaseVO.getTestDataMandatoryFields().put(returnValue.getProperty(TESTMETHOD_NAME), mandatoryFields); returnValue.setProperty(METHOD_SIGNATURE, signature.toString()); returnValue.setProperty(METHOD_PARAMETER_VALUES, paramValues.toString()); Type returnType = methodDocs[index].returnType(); if(returnType != null ){ returnValue.setProperty(METHOD_RETURNTYPE, getReturnTypeName(returnType)); //if return type is not simple type, then add that in test case imports list if(!isSimpleType(returnType)) { testMethodVO.getImportsSet().add(returnType.qualifiedTypeName()); } } } else { // not the first overloaded method (multiple methods sharing one name and one test) returnValue = null; } } // no else LOG.debug("testData map:"+testCaseVO.getTestData()); return returnValue; } private String getReturnTypeName(Type returnType) { String returnTypeName = returnType.simpleTypeName(); if(returnType.dimension() != null && returnType.dimension().equals("[]")){ returnTypeName = returnTypeName.concat("[]"); } return returnTypeName; } /** * generate the code snippet for the converters and editors * * @param returnValue * @param type * @param testCaseVO * @param data * @param parameterName * @param testMethodVO * @param mandatoryFields */ private void codeConvertersAndEditors(Properties returnValue, Type type, TestCaseVO testCaseVO, Map<String, Object> data, String parameterName, TestMethodVO testMethodVO,List<String> mandatoryFields) { LOG.debug("codeConvertersAndEditors started :"+type.qualifiedTypeName()); LOG.debug("isSimpleType:"+isSimpleType(type)); LOG.debug("isTypeEditor:"+isTypeEditor(type)); if(isSimpleType(type) || isJavaDateType(type) || type.asClassDoc().isEnum()){ Object defaultObject = getDefaultObjValue(type); data.put(parameterName, defaultObject); mandatoryFields.add(parameterName); } //commenting editors /*else if(isTypeEditor(type) && !testCaseVO.getConvertersMap().containsKey(type.typeName()+EDITOR_CLASS_NAME_SUFFIX)){ LOG.debug("Type requires Editor:"+type.qualifiedTypeName()); codeEditors(returnValue,type,testCaseVO,testMethodVO); // TODO check parameter type and add default values of that type data.put(parameterName, "defaultString"); mandatoryFields.add(parameterName); } */ else if(!testCaseVO.getConvertersMap().containsKey(type.typeName()+CONVERTER_CLASS_NAME_SUFFIX)){ LOG.debug("Type is not primitive, String:"+type.qualifiedTypeName()); codeConverterClasses(returnValue,type,parameterName,testCaseVO,data,testMethodVO,mandatoryFields); //setting complex parameter type in method data //following method gets all fields in side that complex type and add it to method data map //setTypeFieldsInMethodData(returnValue,type,data); } else { LOG.debug("Type is not matched with any condition:"+type.qualifiedTypeName()); } LOG.debug("codeConvertersAndEditors setter data:"+data); LOG.debug("codeConvertersAndEditors finished :"+type.qualifiedTypeName()); } private Object getDefaultObjValue(Type type) { Object objValue = null; String typeName = type.qualifiedTypeName(); long now = System.currentTimeMillis(); if(typeName.equals("int") || typeName.equals("short") || typeName.equals("long") || typeName.equals("float") || typeName.equals("double") || typeName.equals("java.lang.Integer") || typeName.equals("java.lang.Short") || typeName.equals("java.lang.Long") || typeName.equals("java.lang.Float") || typeName.equals("java.lang.Double")){ objValue = 0; } else if(typeName.equals("java.lang.String")){ objValue = "defaultString"; } else if(typeName.equals("java.lang.Character")){ objValue = null; } else if(typeName.equals("char")){ objValue = null; } else if(typeName.equals("java.lang.Boolean") || typeName.equals("boolean")){ objValue = false; } else if(typeName.equals("java.lang.Byte")){ objValue = new Byte((byte) 0); } else if(typeName.equals("byte")){ objValue = (byte) 0; } else if(typeName.equals("java.util.Date")){ objValue = new Date(now); } else if(typeName.equals("java.sql.Date")){ objValue = new java.sql.Date(now); } else if(typeName.equals("java.sql.Time")){ objValue = new java.sql.Time(now); } else if(typeName.equals("java.sql.Date")){ objValue = new java.sql.Timestamp(now); } return objValue; } private void codeEditors(Properties returnValue, Type type, TestCaseVO testCaseVO, TestMethodVO testMethodVO) { LOG.debug("codeEditors started :"+type.qualifiedTypeName()); returnValue.setProperty(EDITOR_CLASS_NAME, type.simpleTypeName()+EDITOR_CLASS_NAME_SUFFIX); returnValue.setProperty(EDITOR_INSTANCE_TYPE_FULLNAME, type.qualifiedTypeName()); returnValue.setProperty(EDITOR_INSTANCE_TYPE, type.simpleTypeName()); String editorSetValue = ""; if(isTypeJodaDateTime(type)){ String template = getTemplate(returnValue, "editor", "setvaluejodadatetime"); editorSetValue = StringHelper.replaceVariables(template, returnValue); } returnValue.setProperty(EDITOR_SETVALUE, editorSetValue); String template = getTemplate(returnValue, "editor", "default"); LOG.debug("template:"+template); StringBuffer editorCode = new StringBuffer(); editorCode.append(StringHelper.replaceVariables(template, returnValue)); testCaseVO.getConvertersMap().put(returnValue.getProperty(EDITOR_CLASS_NAME),editorCode); testMethodVO.getImportsSet().add(type.qualifiedTypeName()); LOG.debug("codeEditors finsihed :"+type.qualifiedTypeName()); } /** * Generate the code snippet for the required converter classes * * It reads the the attributes from classDoc of the converter class and use the setters of all those attributes, * and constructs the converter class. * * @param returnValue * @param type * @param parameterName * @param testCaseVO * @param data * @param testMethodVO * @param mandatoryFields */ private void codeConverterClasses(Properties returnValue, Type type,String parameterName, TestCaseVO testCaseVO, Map<String, Object> data, TestMethodVO testMethodVO,List<String> mandatoryFields) { LOG.debug("codeConverterClasses started :"+type.qualifiedTypeName()); returnValue.setProperty(CONVERTER_CLASS_NAME, type.typeName()+CONVERTER_CLASS_NAME_SUFFIX); returnValue.setProperty(CONVERTER_INSTANCE_TYPE, type.typeName()); returnValue.setProperty(CONVERTER_INSTANCE_NAME, getInstanceNameForTypeName(type.typeName())); Set<String> converterImportsSet = new HashSet<String>(); returnValue.setProperty(CONVERTER_SETTERS, codeConverterSetters(type,parameterName,returnValue,data,testCaseVO, testMethodVO,converterImportsSet,mandatoryFields)); returnValue.setProperty(CONVERTER_IMPORTS, codeImports(returnValue,converterImportsSet)); StringBuffer converterCode = new StringBuffer(); String template = getTemplate(returnValue, "converter", "default"); LOG.debug("template:"+template); converterCode.append(StringHelper.replaceVariables(template, returnValue)); LOG.debug("converterCode:"+converterCode); testCaseVO.getConvertersMap().put(returnValue.getProperty(CONVERTER_CLASS_NAME), converterCode); LOG.debug("codeConverterClasses finished :"+type.qualifiedTypeName()); } private String getInstanceNameForTypeName(String typeName) { return typeName.substring(0,1).toLowerCase()+typeName.substring(1); } private String codeConverterSetters(Type type,String parameterName, Properties returnValue,Map<String, Object> data, TestCaseVO testCaseVO, TestMethodVO testMethodVO, Set<String> converterImportsSet,List<String> mandatoryFields) { LOG.debug("codeConverterSetters started :"+type.qualifiedTypeName()); StringBuffer setterCode = new StringBuffer(); ClassDoc typeClassDoc = type.asClassDoc(); FieldDoc[] typeFields = typeClassDoc.fields(false); String templatePrimitiveConverter = getTemplate(returnValue, "converter", "setmethodprim"); String templateNonPrimConverter = getTemplate(returnValue, "converter", "setmethodnonprim"); String templateEditorConverter = getTemplate(returnValue, "converter", "setmethodeditor"); String templateConverterSetter = getTemplate(returnValue, "converter", "setmethodconverter"); String templateNoConverterSetter = getTemplate(returnValue, "converter", "setmethodnoconverter"); //LOG.debug("template:"+templateStringConverter); LOG.debug("typeFields length"+typeFields.length); converterImportsSet.add(type.qualifiedTypeName()); for(int i=0;i<typeFields.length;i++){ Type fieldType = typeFields[i].type(); String typeName = fieldType.simpleTypeName(); String fieldSetterName = getSetterName(typeClassDoc,typeFields[i].name()); LOG.debug("typeName"+typeName); //if this is static final public field then no need to set this value // hence skip if(!isSettableField(typeFields[i])){ continue; } LOG.debug("fieldType.qualifiedTypeName():"+fieldType.qualifiedTypeName()); LOG.debug("typeFields[i].name():"+typeFields[i].name()); LOG.debug("typeFields[i].name():"+typeFields[i].qualifiedName()); LOG.debug("typeFields[i].isOrdinaryClass():"+typeFields[i].isOrdinaryClass()); LOG.debug("fieldType.dimension():"+fieldType.dimension()); if(isSimpleType(fieldType) || isJavaDateType(fieldType) || isTypeEnum(fieldType)) { LOG.debug("typeFields[i].name():"+typeFields[i].name()); returnValue.setProperty(CONVERTER_INSTANCE_ATTRIBUTE_SETTER_NAME,fieldSetterName); returnValue.setProperty(CONVERTER_INSTANCE_ATTRIBUTE_TYPE, fieldType.simpleTypeName()); returnValue.setProperty(CONVERTER_INSTANCE_ATTRIBUTE_NAME, typeFields[i].name()); //typeFields[i]. if(fieldType.isPrimitive() || isTypeEnum(fieldType)){ returnValue.setProperty(CONVERTER_INSTANCE_ATTRIBUTE_TYPE_WRAPPER, getTypeBoxName(fieldType.simpleTypeName())); setterCode.append(StringHelper.replaceVariables(templatePrimitiveConverter, returnValue)); } else { setterCode.append(StringHelper.replaceVariables(templateNonPrimConverter, returnValue)); } Object defaultObject = getDefaultObjValue(fieldType); data.put(typeFields[i].name(), defaultObject); if(isMandatory(testMethodVO.getMethodSourceCode(),parameterName,typeFields[i].name())){ mandatoryFields.add(typeFields[i].name()); } if(!isSimpleType(fieldType)){ converterImportsSet.add(fieldType.qualifiedTypeName()); } } //commenting editor code now /*else if(isTypeEditor(fieldType)){ if( !testCaseVO.getConvertersMap().containsKey(fieldType.typeName()+EDITOR_CLASS_NAME_SUFFIX)){ LOG.debug("Type requires Editor:"+fieldType.qualifiedTypeName()); codeEditors(returnValue,fieldType,testCaseVO,testMethodVO); } LOG.debug("typeFields[i].name() editor setter:"+typeFields[i].name()); returnValue.setProperty(CONVERTER_INSTANCE_ATTRIBUTE_SETTER_NAME, fieldSetterName); returnValue.setProperty(CONVERTER_INSTANCE_ATTRIBUTE_TYPE, fieldType.simpleTypeName()); returnValue.setProperty(CONVERTER_INSTANCE_ATTRIBUTE_NAME, typeFields[i].name()); returnValue.setProperty(EDITOR_CLASS_NAME, typeName+EDITOR_CLASS_NAME_SUFFIX); setterCode.append(StringHelper.replaceVariables(templateEditorConverter, returnValue)); converterImportsSet.add(fieldType.qualifiedTypeName()); // TODO check parameter type and add default values of that type data.put(typeFields[i].name(), "defaultString"); if(isMandatory(testMethodVO.getMethodSourceCode(),parameterName,typeFields[i].name())){ mandatoryFields.add(typeFields[i].name()); } }*/ else { //this field is of complex type, hence need to create a converter and set the value //check if converter exist for this complex type, otherwise create it. if(testCaseVO.getConvertersMap().containsKey(fieldType.typeName()+CONVERTER_CLASS_NAME_SUFFIX)){ LOG.debug("set the converter of:"+typeFields[i].name()); returnValue.setProperty(CONVERTER_INSTANCE_ATTRIBUTE_SETTER_NAME, fieldSetterName); returnValue.setProperty(CONVERTER_INSTANCE_ATTRIBUTE_CONVERTER, typeName+CONVERTER_CLASS_NAME_SUFFIX); setterCode.append(StringHelper.replaceVariables(templateConverterSetter, returnValue)); converterImportsSet.add(fieldType.qualifiedTypeName()); } else { LOG.debug("set the converter of:"+typeFields[i].name()); returnValue.setProperty(CONVERTER_INSTANCE_ATTRIBUTE_SETTER_NAME, fieldSetterName); returnValue.setProperty(CONVERTER_INSTANCE_ATTRIBUTE_CONVERTER, typeName+CONVERTER_CLASS_NAME_SUFFIX); setterCode.append(StringHelper.replaceVariables(templateNoConverterSetter, returnValue)); // TODO : need to create converters. /* LOG.debug("Type requires converter:"+fieldType.qualifiedTypeName()); Properties properties = new Properties(returnValue); //using recursion if field type is complex codeConvertersAndEditors(properties,fieldType,convertersMap,data,typeFields[i].name(),importsSet); */ } } } LOG.debug("setterCode:"+setterCode); LOG.debug("codeConverterSetters started :"+type.qualifiedTypeName()); LOG.debug("converter setter data:"+data); return setterCode.toString(); } private boolean isTypeEnum(Type fieldType) { boolean isEnum = false; if(isSimpleType(fieldType)){ isEnum = false; } else if(fieldType.asClassDoc().isEnum() || fieldType.asClassDoc().isFinal() || fieldType.asClassDoc().isEnumConstant()) { isEnum = true; } return isEnum; } /** * Checks the occurrence of getter of a particular attribute of a object in side the method source code, * if presents then treats it as mandatory field to be included in test data generation * * @param methodSourceCode * @param objectName * @param name * @return */ private boolean isMandatory(StringBuffer methodSourceCode, String objectName,String name) { LOG.debug("isMandatory started: objectName"+objectName+", fieldName:"+name); LOG.debug("methodSourceCode:"+methodSourceCode.toString()); String[] methodSourceLines = methodSourceCode.toString().split("\n"); String searchString = objectName+"."+"get"+name; for(String line:methodSourceLines){ if(line!=null && line.toUpperCase().contains(searchString.toUpperCase())){ return true; } } return false; } private String getSetterName(ClassDoc typeClassDoc, String fieldName) { LOG.debug("getSetterName started:"+typeClassDoc.name()+fieldName); String setterName = null; MethodDoc[] methods = typeClassDoc.methods(); for(MethodDoc method:methods){ String methodName = method.name(); if(methodName.contains("set") && methodName.toUpperCase().contains(fieldName.toUpperCase())){ setterName = methodName; break; } } if(setterName == null){ setterName = StringHelper.getSetterName(fieldName); } LOG.debug("getSetterName finished:"+setterName); return setterName; } private String getEditorRegisters(Properties returnValue, Map<String, StringBuffer> convertersMap) { String editorsRegisterStr = ""; if(convertersMap.size() > 0){ StringBuffer editorsRegister = new StringBuffer(); String template = getTemplate(returnValue, "editor", "register"); for(String editorClass:convertersMap.keySet()){ if(editorClass.endsWith(EDITOR_CLASS_NAME_SUFFIX)) { returnValue.setProperty(EDITOR_CLASS_NAME, editorClass); String editorInstanceName = editorClass.replace(EDITOR_CLASS_NAME_SUFFIX, ""); returnValue.setProperty(EDITOR_INSTANCE_TYPE, editorInstanceName); editorsRegister.append(StringHelper.replaceVariables(template, returnValue)); } } editorsRegisterStr = editorsRegister.toString(); } LOG.debug("editorsRegisterStr:"+editorsRegisterStr); return editorsRegisterStr; } private String getConverterRegisters(Properties returnValue, Map<String, StringBuffer> convertersMap) { String convertersRegisterStr = ""; if(convertersMap.size() > 0){ StringBuffer convertersRegister = new StringBuffer(); String template = getTemplate(returnValue, "converter", "register"); for(String converterClass:convertersMap.keySet()){ if(converterClass.endsWith(CONVERTER_CLASS_NAME_SUFFIX)) { returnValue.setProperty(CONVERTER_CLASS_NAME, converterClass); convertersRegister.append(StringHelper.replaceVariables(template, returnValue)); } } convertersRegisterStr = convertersRegister.toString(); } LOG.debug("convertersRegister.toString():"+convertersRegisterStr); return convertersRegisterStr; } private boolean isSimpleType(Type type) { //check if it primitive type or wrapper simple types in java.lang package // i.e. int, Integer,String etc.. return type.isPrimitive() || type.qualifiedTypeName().contains("java.lang."); } //checks if this type is avaialble in java.lang or java.util or java.sql private boolean isJavaDateType(Type type) { boolean isJavaDateType = false; String typeName = type.qualifiedTypeName(); if(isSimpleType(type)) { isJavaDateType = true; } else if (typeName.equals("java.sql.Timestamp")){ isJavaDateType = true; } else if(typeName.equals("java.sql.Date")) { isJavaDateType = true; } else if(typeName.equals("java.sql.Time")) { isJavaDateType = true; } else if(typeName.equals("java.util.Date")) { isJavaDateType = true; } else { isJavaDateType = false; } return isJavaDateType; } private boolean isTypeEditor(Type fieldType) { boolean isTypeEditor = false; if(isJavaDateType(fieldType)) { isTypeEditor = false; } else { isTypeEditor = isTypeJodaDateTime(fieldType); } return isTypeEditor; } private boolean isSettableField(FieldDoc typeFields) { boolean isSettableField = true; ///ClassDoc fieldTypeClassDoc = typeFields.asClassDoc(); if(typeFields != null && (typeFields.isStatic() || typeFields.isFinal())){ isSettableField = false; } if(typeFields!=null && (typeFields.type().dimension() != null && typeFields.type().dimension().equals("[]"))){ isSettableField = false; } return isSettableField; } private boolean isTypeJodaDateTime(Type type) { //checking enum or constant String typeName = type.qualifiedTypeName(); return "org.joda.time.DateTime".equals(typeName); } private String getTypeBoxName(String typeName) { String typeBoxName = null; if("int".equals(typeName)){ typeBoxName = "Integer"; } else if("short".equals(typeName)){ typeBoxName = "Short"; } else if("float".equals(typeName)){ typeBoxName = "Float"; } else if("double".equals(typeName)){ typeBoxName = "Double"; } else if("long".equals(typeName)){ typeBoxName = "Long"; }else if("boolean".equals(typeName)){ typeBoxName = "Boolean"; }else if("byte".equals(typeName)){ typeBoxName = "Byte"; }else if("char".equals(typeName)){ typeBoxName = "Character"; }else { typeBoxName = typeName; } return typeBoxName; } private String getConverterUtilMethodName(String typeName){ typeName = getTypeBoxName(typeName); String utilMethodName = null; if(typeName.endsWith("Integer")){ utilMethodName = "convertToInteger"; } else if(typeName.endsWith("Short")){ utilMethodName = "convertToShort"; } else if(typeName.endsWith("Long")){ utilMethodName = "convertToLong"; } else if(typeName.endsWith("Float")){ utilMethodName = "convertToFloat"; } else if(typeName.endsWith("Double")){ utilMethodName = "convertToDouble"; } else if(typeName.equals("java.util.Date") || typeName.equals("Date") ){ utilMethodName = "convertToDate"; } else if(typeName.equals("java.sql.Date")){ utilMethodName = "convertToSQLDate"; } else if(typeName.equals("java.sql.Time")){ utilMethodName = "convertToSQLTime"; } else if(typeName.equals("java.sql.Timestamp")){ utilMethodName = "convertToSQLTimestamp"; } else if(typeName.endsWith("Boolean")){ utilMethodName = "convertToBoolean"; } else if(typeName.endsWith("Byte")){ utilMethodName = "convertToByte"; } else if(typeName.endsWith("Character")){ utilMethodName = "convertToCharacter"; } return utilMethodName; } /** * Generate the test suite by checking if sub packages test suites exists * * @param testSuiteVO * @param indexPackage * @return */ public String getTestSuiteAddTestSuites(TestSuiteVO testSuiteVO, int indexPackage) { StringBuffer sb; String template; String templateForNormalItem; String templateForLastItem; Properties addProps; PackageDoc[] subPackages; sb = new StringBuffer(); addProps = new Properties(testSuiteVO.getProperties()); templateForNormalItem = getTemplate(testSuiteVO.getProperties(), ADD_TESTSUITE_TO_TESTSUITE, TEMPLATE_ATTRIBUTE_DEFAULT); templateForLastItem = getTemplate(testSuiteVO.getProperties(), ADD_TESTSUITE_TO_TESTSUITE, TEMPLATE_ATTRIBUTE_DEFAULT_LAST); subPackages = getDirectSubPackages(testSuiteVO.getPackageDocs(), indexPackage); List<String> testSuiteList = testSuiteVO.getTestSuiteClasses(); for (int i=0;i<testSuiteList.size();i++) { if( i==testSuiteList.size()-1 && isNotEmpty(templateForLastItem) ) { template = templateForLastItem; } else { template = templateForNormalItem; } if (isTestSuiteDirectSubPackage(testSuiteList.get(i),subPackages)) { addProps.setProperty(ADD_TESTSUITE_NAME,testSuiteList.get(i)); //addProps.setProperty(TESTSUITE_PACKAGE_NAME, testSuiteVO.getNaming().getTestPackageName(subPackages[i].name())); sb.append(StringHelper.replaceVariables(template, addProps)); } } return sb.toString(); } private boolean isTestSuiteDirectSubPackage(String testSuiteName, PackageDoc[] subPackages) { for(PackageDoc subPackageDoc:subPackages){ if(testSuiteName.contains(subPackageDoc.name())) { return true; } } return false; } /** * adds test cases to test suite * * @param testSuiteVO * @param indexPackage * @return */ public String getTestSuiteAddTestCases(TestSuiteVO testSuiteVO, int indexPackage) { StringBuffer sb; String template; String templateForNormalItem; String templateForLastItem; Properties addProps; //ClassDoc[] classes; sb = new StringBuffer(); addProps = new Properties(testSuiteVO.getProperties()); templateForNormalItem = getTemplate(testSuiteVO.getProperties(), ADD_TESTCASE_TO_TESTSUITE, TEMPLATE_ATTRIBUTE_DEFAULT); templateForLastItem = getTemplate(testSuiteVO.getProperties(), ADD_TESTCASE_TO_TESTSUITE, TEMPLATE_ATTRIBUTE_DEFAULT_LAST); //classes = testSuiteVO.getPackageDocs()[indexPackage].ordinaryClasses(); // If there are any testsuites, they are placed behind the testcases. // In such case, we never use the template for last testcase (the one without trailing ','). if( isTestSuiteExist(testSuiteVO,indexPackage)) { templateForLastItem = null; } List<String> testClassNames = testSuiteVO.getTestClasses(); for (int i=0; i<testClassNames.size(); i++) { //if (isTestableClass(classes[i], testSuiteVO.getNaming())) { addProps.setProperty(ADD_TESTCASE_NAME, testClassNames.get(i)); //addProps.setProperty(TESTSUITE_PACKAGE_NAME, testSuiteVO.getNaming().getTestPackageName(testSuiteVO.getPackageDocs()[indexPackage].name())); //sb.append(StringHelper.replaceVariables(template, addProps)); if(i<testClassNames.size()-1){ sb.append(StringHelper.replaceVariables(templateForNormalItem, addProps)); } else { if(templateForLastItem!= null) { sb.append(StringHelper.replaceVariables(templateForLastItem, addProps)); } else { sb.append(StringHelper.replaceVariables(templateForNormalItem, addProps)); } } //} } return sb.toString(); } private boolean isTestSuiteExist(TestSuiteVO testSuiteVO, int indexPackage) { String testSuites = getTestSuiteAddTestSuites(testSuiteVO, indexPackage); if(testSuites != null && !"".equals(testSuites)){ return true; } return false; } /** * generates the test suite imports * * @param testSuiteVO * @return */ public String getTestSuiteImports(TestSuiteVO testSuiteVO) { StringBuffer sb; String template; Properties addProps; sb = new StringBuffer(); addProps = new Properties(testSuiteVO.getProperties()); template = getTemplate(testSuiteVO.getProperties(), ADD_IMPORT_TESTSUITE, TEMPLATE_ATTRIBUTE_DEFAULT); for (String testSuiteName:testSuiteVO.getTestSuiteClasses()) { addProps.setProperty(ADD_TESTSUITE_NAME, testSuiteName); //addProps.setProperty(TESTSUITE_PACKAGE_NAME, testSuiteVO.getNaming().getTestPackageName(subPackages[i].name())); sb.append(StringHelper.replaceVariables(template, addProps)); } return sb.toString(); } public boolean isFirstTestableMethodWithName(MethodDoc[] methodDocs, int index) { boolean returnValue = true; String reference; reference = methodDocs[index].name(); for (int i=0; (i<index) && returnValue; i++) { if (reference.equals(methodDocs[i].name()) && isTestableMethod(methodDocs[i])) { returnValue = false; } } return returnValue; } public int countTestableMethodsWithName(MethodDoc[] methodDocs, String methodName) { int returnValue =0; for (int i=0; (i<methodDocs.length); i++) { if (methodName.equals(methodDocs[i].name()) && isTestableMethod(methodDocs[i])) { returnValue++; } } return returnValue; } /** * Builds accessor specific properties if the method specified by 'index' is an accessor method. * * @return if specfied method is an set accessor, returns properties with all properties from * parameter 'properties' and accessor specific properties; * if specfied method is an get accessor, return null; * if specfied method is not an accessor, returns parameter 'properties' unchanged */ public Properties getTestAccessorProperties(TestCaseVO testCaseVO, TestMethodVO testMethodVO,int index) { Properties returnValue = null; String testMethodName; String methodName; int indexAccessorPair; int indexArray; String accessedPropertyName; String setAccessorName; String getAccessorName; String testsByType; String accessorTypeName; Parameter[] parameters; MethodDoc[] methodDocs = testMethodVO.getMethodDocs(); methodName = methodDocs[index].name(); indexAccessorPair = getAccessorPairIndex(methodDocs, index); if (indexAccessorPair >= 0) { if ((methodName.startsWith(ACCESSOR_STARTS_WITH[indexAccessorPair][INDEX_SET])) && (isFirstTestableMethodWithName(methodDocs, index))) { // testSetGet accessedPropertyName = getAccessedPropertyName(methodName, indexAccessorPair); if ((accessedPropertyName != null) && (accessedPropertyName.length() > 0)) { testMethodName = testCaseVO.getNaming().getTestAccessorName(ACCESSOR_STARTS_WITH[indexAccessorPair][INDEX_SET], ACCESSOR_STARTS_WITH[indexAccessorPair][INDEX_GET], accessedPropertyName); setAccessorName = ACCESSOR_STARTS_WITH[indexAccessorPair][INDEX_SET]+accessedPropertyName; getAccessorName = ACCESSOR_STARTS_WITH[indexAccessorPair][INDEX_GET]+accessedPropertyName; parameters = methodDocs[index].parameters(); if ((parameters != null) && (parameters.length == 1)) { accessorTypeName = parameters[0].typeName(); indexArray = accessorTypeName.indexOf("[]"); if (indexArray == -1) { testsByType = getAccessorTestsByType(testMethodVO.getProperties(), TEMPLATE_ATTRIBUTE_DEFAULT, accessorTypeName); } else{ testsByType = getAccessorTestsByType(testMethodVO.getProperties(), TEMPLATE_ATTRIBUTE_ARRAY, accessorTypeName.substring(0, indexArray)); } returnValue = new Properties(testMethodVO.getProperties()); returnValue.setProperty(ACCESSOR_TESTS, testsByType); returnValue.setProperty(ACCESSOR_NAME, testMethodName); returnValue.setProperty(ACCESSOR_SET_NAME, setAccessorName); returnValue.setProperty(ACCESSOR_GET_NAME, getAccessorName); returnValue.setProperty(ACCESSOR_TYPE_NAME, accessorTypeName); returnValue.setProperty(TESTMETHOD_NAME, testMethodName); returnValue.setProperty(TEMPLATE_NAME, TEMPLATE_ATTRIBUTE_ACCESSOR); returnValue.setProperty(METHOD_NAME, methodDocs[index].name()); } } else { // method is not an accessor returnValue = testMethodVO.getProperties(); } } if (methodName.startsWith(ACCESSOR_STARTS_WITH[indexAccessorPair][INDEX_GET])) { // if method is a get-accessor and there is a set accessor -> nothing to do here returnValue = null; } } else { returnValue = testMethodVO.getProperties(); } return returnValue; } /** * A method is considered an accessor if (i) method name starts with certain prefixes, * (ii) prefix is followed by a property name (that is longer than the empyt string ""), * (iii) there are methods with this property name for both 'get' and 'set' prefixes, * (iv) number of parameters for the get method is 0 and number of parameter for the set method is 1. * * @return -1 = not both accessors found or not an accessor method, * 0 or above = index of prefix in ACCESSOR_STARTS_WITH method of the method specified by 'index' */ public int getAccessorPairIndex(MethodDoc[] methodDocs, int index) { int returnValue = -1; String accessedPropertyName; String setAccessorName; String getAccessorName; boolean foundSet = false; boolean foundGet = false; boolean exactlyOneParamSet = true; boolean exactlyZeroParamGet = true; if (isTestableMethod(methodDocs[index])) { for (int i = 0; (returnValue == -1) && (i < ACCESSOR_STARTS_WITH.length); i++) { accessedPropertyName = getAccessedPropertyName(methodDocs[index].name(), i); if ((accessedPropertyName != null) && (accessedPropertyName.length() > 0 )) { setAccessorName = ACCESSOR_STARTS_WITH[i][INDEX_SET]+accessedPropertyName; getAccessorName = ACCESSOR_STARTS_WITH[i][INDEX_GET]+accessedPropertyName; for (int j=0; (returnValue == -1) && (j<methodDocs.length); j++) { if (isTestableMethod(methodDocs[j])) { if (getAccessorName.equals(methodDocs[j].name())) { foundGet |= true; exactlyZeroParamGet &= (getNumberOfParameters(methodDocs[j]) == 0); } else if (setAccessorName.equals(methodDocs[j].name())) { foundSet |= true; exactlyOneParamSet &= (getNumberOfParameters(methodDocs[j]) == 1); } } // no else } if (foundGet && foundSet && exactlyOneParamSet && exactlyZeroParamGet) { returnValue = i; } // no else } // no else, is not an accessor method } } // no else return returnValue; } /** * Comment on DBC:<br> * \@pre methodDoc != null <br> */ private static int getNumberOfParameters(MethodDoc methodDoc) { if (methodDoc.parameters() != null) { return methodDoc.parameters().length; } else { return 0; } } /** * @return name of accessed property if 'accessorMethodName' starts with an accessor prefix * specified by 'indexAccessorPair' (see field ACCESSOR_STARTS_WITH), * null in all other cases. */ public String getAccessedPropertyName(String accessorMethodName, int indexAccessorPair) { String returnValue = null; String prefix; if ((accessorMethodName != null) && (accessorMethodName.length()>0)) { for (int setOrGet =0; ((returnValue == null) && (setOrGet<ACCESSOR_STARTS_WITH[indexAccessorPair].length)); setOrGet++) { prefix = ACCESSOR_STARTS_WITH[indexAccessorPair][setOrGet]; if (accessorMethodName.startsWith(prefix)) { returnValue = accessorMethodName.substring(prefix.length()); } } } return returnValue; } public String getAccessorTestsByType(Properties properties, String templateAttribute, String type) { String returnValue = null; String template; Properties addProps; if (TEMPLATE_ATTRIBUTE_DEFAULT.equals(templateAttribute)) { returnValue = properties.getProperty(ACCESSOR_TESTS + "." + type); } if (returnValue == null) { template = getTemplate(properties, ACCESSOR_TESTS, templateAttribute); addProps = new Properties(properties); addProps.put(ACCESSOR_TYPE_NAME, type); returnValue = StringHelper.replaceVariables(template, addProps); } if (returnValue != null) { returnValue = returnValue.trim(); } return returnValue; } public boolean isInnerClass(ClassDoc doc) { boolean returnValue = false; if (doc != null) { returnValue = (-1 < doc.name().indexOf(".")); } return returnValue; } public boolean isATest(ClassDoc doc) { boolean returnValue = false; ClassDoc temp; String tempName; ClassDoc interfaces[]; temp = doc; // iterate over this class and all super classes while (!returnValue && (temp != null)) { tempName = temp.qualifiedName(); if (tempName.equals(JUNIT_TEST_CLASS_NAME)) { returnValue = true; // Is junit.framework.Test a super class? (true for very old versions of JUnit) } else { interfaces = temp.interfaces(); // iterate over all interfaces for (int i=0; ((interfaces != null) && (i<interfaces.length)); i++) { tempName = interfaces[i].qualifiedName(); if (tempName.equals(JUNIT_TEST_CLASS_NAME)) { returnValue = true; // Is this class or any super class implementing junit.framework.Test? } } } temp = temp.superclass(); } return returnValue; } public boolean hasSuiteMethod(ClassDoc doc) { boolean returnValue = false; MethodDoc[] methods = doc.methods(); for (int i = 0; !returnValue && (i < methods.length); i++) { MethodDoc method = methods[i]; returnValue |= TESTSUITE_SUITE_METHOD_NAME.equals(method.name()) && method.isStatic(); } return returnValue; } public PackageDoc[] getDirectSubPackages(PackageDoc[] packageDocs, int indexCurrentPackage) { List<PackageDoc> list; String subStart; String tempPackageName; list = new LinkedList<PackageDoc>(); subStart = packageDocs[indexCurrentPackage].name()+ "."; for (int i=0; i< packageDocs.length; i++) { tempPackageName = packageDocs[i].name(); if ((i != indexCurrentPackage) && // is not current tempPackageName.startsWith(subStart) && // is sub package (may be indirect) (-1 == tempPackageName.indexOf(".", subStart.length()))) { // is direct sub package (no further ".") list.add(packageDocs[i]); } } return (PackageDoc[]) list.toArray( new PackageDoc[0]); } public boolean isValid(String code) { return hasAllRequiredStrings(code) && isValidStructure(code); } public boolean hasAllRequiredStrings(String code) { boolean returnValue = true; // create array w/ required strings if (requiredStrings == null) { requiredStrings = new String[MINIMUM_MARKER_SET.length]; for (int i = 0; i < MINIMUM_MARKER_SET.length; i++) { requiredStrings[i] = MINIMUM_MARKER_SET[i].trim(); } } // check if code contains all required markers for (int i = 0; i < requiredStrings.length; i++) { if (code.indexOf(requiredStrings[i]) == -1) { returnValue = false; } } return returnValue; } public boolean isValidStructure(String code) { boolean returnValue = true; int indexBegin; int indexEnd; int indexContentBegin; int indexContentEnd; String markDescription; if (code != null) { indexBegin = code.indexOf(VALUE_MARKER_BEGIN); indexEnd = code.indexOf(VALUE_MARKER_END); while (returnValue && (indexBegin < indexEnd) && (indexBegin > -1)) { markDescription = code.substring(indexBegin + VALUE_MARKER_BEGIN.length(), code.indexOf("\n", indexBegin)); indexEnd = indexBegin+VALUE_MARKER_BEGIN.length(); do { indexEnd = code.indexOf(VALUE_MARKER_END + markDescription, indexEnd); } while ((indexEnd>0) && (Character.isWhitespace(code.charAt(indexEnd)))); if (indexEnd > -1) { indexContentBegin = code.indexOf("\n", indexBegin+VALUE_MARKER_BEGIN.length()); indexContentEnd = code.lastIndexOf("\n", indexEnd); if (indexContentBegin < indexContentEnd) { returnValue = isValidStructure(code.substring(indexContentBegin, indexContentEnd)); } } else { returnValue = false; } indexBegin = code.indexOf(VALUE_MARKER_BEGIN, indexEnd+1); indexEnd = code.indexOf(VALUE_MARKER_END, indexEnd+1); } returnValue = returnValue && (indexBegin * indexEnd > 0); // existing pairwise, if existing at all returnValue = returnValue && ((indexBegin<0) || (indexBegin<indexEnd)); } else { printError("TestingStrategy.isValidStructure() code == null"); returnValue = false; } return returnValue; } /** * Merges all markers from inCode into inOutCode. In the end all markers from oldCode * will be in newCode as well. If nessesary some new generated default content in * newCode gets overwritten. If some markers are not in newCode any more, they will * be moved to testVault, a special test method. * * @param inOutCode points to the in-out StringBuffer with the new code * @param inCode holds all markers to be merged into to newCode * @param fullClassName is used only for the error message, if anything goes wrong. * @return true if successfully merged, false if old code contains no JUnitDoclet markers. */ public boolean merge(StringBuffer inOutCode, StringBuffer inCode, String fullClassName) { boolean returnValue = true; String newContent; String oldContent; String markDescription; String markContent; int oldIndexLeft; int oldIndexRight; int insertFromIndex; int insertToIndex; StringBuffer unmatched; if (inOutCode != null) { if (inCode != null) { oldContent = inCode.toString(); unmatched = new StringBuffer(); oldIndexLeft = oldContent.indexOf(VALUE_MARKER_BEGIN, 0); oldIndexRight = oldContent.indexOf("\n", oldIndexLeft) + "\n".length(); if (isValid(oldContent)) { while ((oldIndexRight > -1) && (oldIndexLeft > -1)) { markDescription = oldContent.substring(oldIndexLeft + VALUE_MARKER_BEGIN.length(), oldIndexRight).trim(); oldIndexLeft = oldIndexRight; oldIndexRight = oldContent.indexOf(VALUE_MARKER_END + markDescription, oldIndexLeft); oldIndexRight = oldContent.lastIndexOf("\n", oldIndexRight) + "\n".length(); markContent = oldContent.substring(oldIndexLeft, oldIndexRight); newContent = inOutCode.toString(); insertFromIndex = 0; do { insertFromIndex = newContent.indexOf(VALUE_MARKER_BEGIN + markDescription, insertFromIndex); if (insertFromIndex > -1) { insertFromIndex = insertFromIndex + VALUE_MARKER_BEGIN.length() + markDescription.length(); } } while ((insertFromIndex > -1) && (!Character.isWhitespace(newContent.charAt(insertFromIndex)))); if (insertFromIndex > -1) { // go to end of line while ((insertFromIndex - 1 < newContent.length()) && (newContent.charAt(insertFromIndex - 1) != '\n')) { insertFromIndex++; } insertToIndex = newContent.indexOf(VALUE_MARKER_END + markDescription, insertFromIndex); } else { insertToIndex = -1; } // go back to begin of line while ((insertToIndex > 0) && (newContent.charAt(insertToIndex - 1) == ' ')) { insertToIndex--; } if ((insertFromIndex != -1) && (insertToIndex != -1)) { if (containsCodeOrComment(markContent)) { // replace only, if old marker was empty inOutCode.replace(insertFromIndex, insertToIndex, markContent); } // no else } else { // no match found -> append special method if there is some content if (containsCodeOrComment(markContent)) { unmatched.append(VALUE_MARKER_BEGIN + markDescription); unmatched.append("\n"); unmatched.append(markContent); unmatched.append(VALUE_MARKER_END + markDescription); unmatched.append("\n"); } // no else } oldIndexLeft = oldContent.indexOf(VALUE_MARKER_BEGIN, oldIndexRight); oldIndexRight = oldContent.indexOf("\n", oldIndexLeft) + "\n".length(); } if (unmatched.length() > 0) { // there have been unmatched blocks newContent = inOutCode.toString(); insertToIndex = newContent.lastIndexOf(VALUE_METHOD_UNMATCHED_NAME); // go back to begin of line while ((insertToIndex > 0) && (newContent.charAt(insertToIndex - 1) != '\n')) { insertToIndex--; } if (insertToIndex != -1) { inOutCode.insert(insertToIndex, unmatched.toString()); } // no else } if (hasUnmatchedMarkers(inOutCode.toString())) { printWarning("Class " + fullClassName + " contains unmatched tests."); } } else { printWarning("Class " + fullClassName + " was not generated by JUnitDoclet. It's not overwritten.\n"+ "Please rename and start JUnitDoclet again."); returnValue = false; } } // no else } else { printError("TestingStrategy.merge() inOutCode == null"); } return returnValue; } public boolean containsCodeOrComment(String markContent) { boolean returnValue = false; char ch; if ((markContent != null) && (markContent.length() > 0)) { for (int i=0; (!returnValue && (i<markContent.length())); i++) { ch = markContent.charAt(i); returnValue = !Character.isWhitespace(ch); } } // no else return returnValue; } public boolean hasUnmatchedMarkers(String code) { boolean returnValue = false; int beginUnmatched; int endUnmatched; int tempUnmatched; beginUnmatched = StringHelper.indexOfTwoPartString(code, VALUE_MARKER_METHOD_BEGIN, VALUE_METHOD_UNMATCHED_NAME_MARKER, 0); endUnmatched = StringHelper.indexOfTwoPartString(code, VALUE_MARKER_METHOD_END, VALUE_METHOD_UNMATCHED_NAME_MARKER, beginUnmatched); // TODO better search algorithm for beginUnmatched and endUnmatched if ((beginUnmatched != -1) && (endUnmatched != -1) && (endUnmatched > beginUnmatched)) { tempUnmatched = beginUnmatched + VALUE_MARKER_METHOD_BEGIN.length() + VALUE_METHOD_UNMATCHED_NAME.length(); while ('\n' != code.charAt(tempUnmatched)) { tempUnmatched++; } while ((tempUnmatched < endUnmatched) && (Character.isWhitespace(code.charAt(tempUnmatched)))) { tempUnmatched++; } if (tempUnmatched < endUnmatched) { returnValue = true; } // no else } return returnValue; } }